package Logic;

import Core.ConfigManager;
import Core.Log4j2;
import microgame.airplane.proto.*;
import microgame.airplane.proto.MsgTypeOuterClass;
import io.netty.channel.Channel;
import microgame.toolset.datatable.DataTable;
import microgame.toolset.datatable.DataTableManager;
import microgame.toolset.datatable.Row;
import za.co.luma.geom.Vector2DDouble;

import java.util.*;


//设计理念：服务器不负责检查客户端欺骗。因此，除了飞机的生成、propstub的生成，是由服务器发起。其他的都是帮助客户端同步而已。
//每个client会在match开始的第一时间得到一个在match中唯一的id值，在这些地方用来判别用户：
//1、propstub属于谁
//2、prop属于谁

public class Match {
    private Hashtable<String, Client> clientMap = new Hashtable<>();
    private int id;
    private int capcity = 0;
    private MatchStatus status;
    private int mapMinX, mapMaxX, mapMinY, mapMaxY, minLengthOfSpawn;
    private int mapId;
    // time when match start
    private long startTime;
    // last time
    private long lastTime;
    // 从配置表里读出的match的时间（关卡时间）
    private float levelTime;
    private PropStubManager propStubManager;
    private PropManager propManager;
    private LinkedList<Vector2DDouble> planeSpawnPoints;
    private LinkedList<Vector2DDouble> propSpawnPoints;
    private int currentPlayerIdToAssign;

    private float accumulateTime = 0;

    public Match(int id, int capcity) {
        this.id = id;
        this.capcity = capcity;
        this.status = MatchStatus.matching;
        this.propStubManager = new PropStubManager(this);
        this.propManager = new PropManager(this);
        this.currentPlayerIdToAssign = 0;
    }

    public int GetId() {
        return id;
    }

    public int GetCapcity() {
        return capcity;
    }

    public MatchStatus GetStatus() {
        return status;
    }

    public boolean IsJoinable(int capcity) {
        if (status == MatchStatus.matching && capcity == this.capcity) {
            return true;
        }

        return false;
    }

    public void AddClientFromId(String id) {
        if (status == MatchStatus.matching) {
            Client client = ClientManager.Instance().GetClient(id);
            if (client != null)
                clientMap.put(id, client);

            if (clientMap.size() == capcity) {
                status = MatchStatus.init;
            }
        }
    }

    public boolean Contains(String id) {
        return (clientMap.get(id) != null);
    }

    public boolean Contains(int playerId) {
        for (Map.Entry<String, Client> entry : clientMap.entrySet()) {
            if (entry.getValue().GetPlayerId() == playerId)
                return true;
        }

        return false;
    }

    public void RemoveClientFromId(String id) {
        clientMap.remove(id);

        if (clientMap.size() < capcity) {
            status = MatchStatus.matching;
        }
    }

    public void ResetClientHeartBeatTimer(String clientId) {
        clientMap.get(clientId).SetHeartBeatTime(0);
    }

    public PropManager GetPropManager() {
        return propManager;
    }

    public PropStubManager GetPropStubManager() {
        return propStubManager;
    }

    public void SendMessageToAllClients(int msgId, byte[] msg) {
        for (Map.Entry<String, Client> entry : clientMap.entrySet()) {
            Channel channel = entry.getValue().GetChannel();
            Util.SendMessageThruChannel(msgId, channel, msg);
        }
    }

    public void SendMessageToClientsExcept(String clientId, int msgId, byte[] msg) {
        for (Map.Entry<String, Client> entry : clientMap.entrySet()) {
            if (entry.getValue().GetId() != clientId) {
                Channel channel = entry.getValue().GetChannel();
                Util.SendMessageThruChannel(msgId, channel, msg);
            }
        }
    }

    public int GetMapId() {
        return mapId;
    }

    public boolean Update() {
        if (status == MatchStatus.init) {
            InitMatch();

            PushMatchStartInfo();
            PushSpawnInfo();

            startTime = System.currentTimeMillis();
            lastTime = startTime;
            status = MatchStatus.running;
            return true;
        }
        else if (status == MatchStatus.running) {
            return MatchRun();
        }

        return true;
    }

    public void SyncAirPlaneTransform(String clientId, float x, float y, float angle) {

        Client client = clientMap.get(clientId);

        Game.sSyncAirPlaneTransformPush.Builder builder = Game.sSyncAirPlaneTransformPush.newBuilder();
        builder.setPlayerid(client.GetPlayerId());
        builder.setX(x);
        builder.setY(y);
        builder.setAngle(angle);
        Game.sSyncAirPlaneTransformPush push = builder.build();
        SendMessageToClientsExcept(clientId, MsgTypeOuterClass.MsgType.sSyncAirPlaneTransformPush.getNumber(), push.toByteArray());
    }

    public LinkedList<Vector2DDouble> GetPropSpawnPoints() {
        return propSpawnPoints;
    }

    private void InitMatch() {
        mapId = FindSuitableMap(capcity);

        DataTable table = DataTableManager.Instance().GetDataTable("map");
        Row row = table.GetRow(mapId);
        levelTime = row.GetFloat("leveltime");

        List<Vector2DDouble> planeSpawnPoints = PointGenerator.Instance().GenSpawnPoints(mapId, 1080);
        List<Vector2DDouble> propSpawnPoints = PointGenerator.Instance().GenSpawnPoints(mapId, 500);

        this.planeSpawnPoints = new LinkedList<>();
        this.propSpawnPoints = new LinkedList<>();

        for (int i  = 0; i < planeSpawnPoints.size(); ++i) {
            this.planeSpawnPoints.push(planeSpawnPoints.get(i));
        }

        for (int i  = 0; i < propSpawnPoints.size(); ++i) {
            this.propSpawnPoints.push(propSpawnPoints.get(i));
        }
    }

    private int FindSuitableMap(int capcity) {
        DataTable dt = DataTableManager.Instance().GetDataTable("map");
        ArrayList<Row> rows = dt.GetAllRows();

        int index = -1;
        for (int i = 0; i < rows.size(); ++i) {
            if (rows.get(i).GetInteger("capcity") == capcity) {
                index = i;
                break;
            }
        }

        if (index != -1) {
            return rows.get(index).GetInteger("id");
        }

        return -1;
    }

    private void PushSpawnInfo() {
        ArrayList<Point2d> spawnPoints = GenerateSpawnPoints();

        float tempx = spawnPoints.get(currentPlayerIdToAssign).x;
        float tempy = spawnPoints.get(currentPlayerIdToAssign).y;

        for (Map.Entry<String, Client> entry : clientMap.entrySet()) {
            Game.sAirPlaneSpawnPush.Builder airPlaneSpawnBuilder = Game.sAirPlaneSpawnPush.newBuilder();

            airPlaneSpawnBuilder.setPlayerid(currentPlayerIdToAssign);
            airPlaneSpawnBuilder.setX(tempx);
            airPlaneSpawnBuilder.setY(tempy);
            airPlaneSpawnBuilder.setAngle(0);
            airPlaneSpawnBuilder.setRemote(false);

            Game.sAirPlaneSpawnPush airPlaneSpawnPush = airPlaneSpawnBuilder.build();
            Util.SendMessageThruChannel(MsgTypeOuterClass.MsgType.sAirPlaneSpawnPush.getNumber(), entry.getValue().GetChannel(), airPlaneSpawnPush.toByteArray());

            // remote: same transforma, but remote tag is true, so client know it a proxy
            airPlaneSpawnBuilder.setRemote(true);

            airPlaneSpawnPush = airPlaneSpawnBuilder.build();
            SendMessageToClientsExcept(entry.getValue().GetId(), MsgTypeOuterClass.MsgType.sAirPlaneSpawnPush.getNumber(), airPlaneSpawnPush.toByteArray());

            entry.getValue().SetPlayerId(currentPlayerIdToAssign);
            currentPlayerIdToAssign++;

            tempx += 400;
            tempy += 600;
        }
    }

    private void PushMatchStartInfo() {
        Game.sMatchStartPush.Builder builder = Game.sMatchStartPush.newBuilder();
        builder.setMapid(mapId);

        Game.sMatchStartPush ack = builder.build();
        byte[] result = ack.toByteArray();
        SendMessageToAllClients(MsgTypeOuterClass.MsgType.sMatchStartPush.getNumber(), result);
    }

    private boolean MatchRun() {
        //这里的单位是毫秒，我们的大多数逻辑是秒，注意换算
        long currentTime = System.currentTimeMillis();
        //本match运行的时间
        long matchRunningTime = currentTime - startTime;
        //帧时间
        long frameTime = currentTime - lastTime;
        //这里把毫秒化成秒，所以在后面都是秒单位
        float deltaTime = ((float) frameTime) / 1000.0f;

        //每个match都有时间限制，在限制的时间内：生成propstub。超时，match结束。
        if (matchRunningTime < levelTime * 1000) {
            PropStubsTicks(deltaTime);

            if (ConfigManager.Instance().getUseHeartBeat() == "true") {
                CheckClientAlive(deltaTime);
            }

            lastTime = currentTime;

            return true;
        }
        else {
            MatchOver();
            return false;
        }
    }

    void CheckClientAlive(float deltaTime) {
        for (Map.Entry<String, Client> entry : clientMap.entrySet()) {
            float time = entry.getValue().GetHeartBeatTime();
            time += deltaTime;

            if (time > ConfigManager.Instance().getHeartBeatTime()) {
                //kick out client
                Game.sPlayerQuitPush.Builder builer = Game.sPlayerQuitPush.newBuilder();
                builer.setPlayerid(entry.getValue().GetPlayerId());
                builer.setReason(0);

                Game.sPlayerQuitPush push = builer.build();
                SendMessageToClientsExcept(entry.getKey(), MsgTypeOuterClass.MsgType.sPlayerQuitPush.getNumber(), push.toByteArray());

                clientMap.remove(entry.getKey());
            }
        }
    }

    public void ProcessPlayerLifeEnd(int playerId) {
        Game.sAirPlaneLifeEndPush.Builder builder = Game.sAirPlaneLifeEndPush.newBuilder();
        builder.setPlayerid(playerId);
        Game.sAirPlaneLifeEndPush push = builder.build();
        SendMessageToAllClients(MsgTypeOuterClass.MsgType.sAirPlaneLifeEndPush.getNumber(), push.toByteArray());
    }

    private void MatchOver() {
        Game.sMatchEndPush.Builder builder = Game.sMatchEndPush.newBuilder();
        Game.sMatchEndPush push = builder.build();
        byte[] result = push.toByteArray();
        SendMessageToAllClients(MsgTypeOuterClass.MsgType.sMatchEndPush.getNumber(), result);
    }

    private ArrayList<Point2d> GenerateSpawnPoints() {
        ArrayList<Point2d> points = new ArrayList<>();
        for (int i = 0; i < capcity; ++i) {
            Vector2DDouble point = planeSpawnPoints.pop();
            points.add(new Point2d(((int) point.x), ((int) point.y)));

            //back to queue
            planeSpawnPoints.offerLast(point);
        }
        return points;
    }

    //这里的时间单位是秒
    private void PropStubsTicks(float frameTime) {
        propStubManager.Tick(frameTime);
    }
}
